"use strict";

const utils = require("./../utils");
const settle = require("./../core/settle");
const buildFullPath = require("../core/buildFullPath");
const buildURL = require("./../helpers/buildURL");
const http = require("http");
const https = require("https");
const httpFollow = require("follow-redirects").http;
const httpsFollow = require("follow-redirects").https;
const url = require("url");
const zlib = require("zlib");
const pkg = require("./../../package.json");
const createError = require("../core/createError");
const enhanceError = require("../core/enhanceError");

const isHttps = /https:?/;

/*eslint consistent-return:0*/
/**
 * 使用Promise包装http请求
 *
 * @param {Object} axios配置
 */
module.exports = function httpAdapter(config) {
  return new Promise(function dispatchHttpRequest(
    resolvePromise,
    rejectPromise
  ) {
    const resolve = function resolve(value) {
      resolvePromise(value);
    };
    const reject = function reject(value) {
      rejectPromise(value);
    };
    const data = config.data;
    const headers = config.headers;

    // 设置User-Agent（某些服务器需要）
    // 仅在未在配置中设置标头的情况下设置标头
    // 参见https://github.com/axios/axios/issues/69
    if (!headers["User-Agent"] && !headers["user-agent"]) {
      headers["User-Agent"] = "axios/" + pkg.version;
    }

    if (data && !utils.isStream(data)) {
      if (Buffer.isBuffer(data)) {
        // 无需进行任何操作...
      } else if (utils.isArrayBuffer(data)) {
        data = Buffer.from(new Uint8Array(data));
      } else if (utils.isString(data)) {
        data = Buffer.from(data, "utf-8");
      } else {
        return reject(
          createError(
            "Data after transformation must be a string, an ArrayBuffer, a Buffer, or a Stream",
            config
          )
        );
      }

      // 添加Content-Length标头（如果存在数据）
      headers["Content-Length"] = data.length;
    }

    // HTTP基本身份验证
    let auth = undefined;
    if (config.auth) {
      const username = config.auth.username || "";
      const password = config.auth.password || "";
      auth = username + ":" + password;
    }

    // 解析网址
    const fullPath = buildFullPath(config.baseURL, config.url);
    const parsed = url.parse(fullPath);
    const protocol = parsed.protocol || "http:";

    if (!auth && parsed.auth) {
      const urlAuth = parsed.auth.split(":");
      const urlUsername = urlAuth[0] || "";
      const urlPassword = urlAuth[1] || "";
      auth = urlUsername + ":" + urlPassword;
    }

    if (auth) {
      delete headers.Authorization;
    }

    const isHttpsRequest = isHttps.test(protocol);
    const agent = isHttpsRequest ? config.httpsAgent : config.httpAgent;

    const options = {
      path: buildURL(
        parsed.path,
        config.params,
        config.paramsSerializer
      ).replace(/^\?/, ""),
      method: config.method.toUpperCase(),
      headers: headers,
      agent: agent,
      agents: { http: config.httpAgent, https: config.httpsAgent },
      auth: auth,
    };

    if (config.socketPath) {
      options.socketPath = config.socketPath;
    } else {
      options.hostname = parsed.hostname;
      options.port = parsed.port;
    }

    let proxy = config.proxy;
    if (!proxy && proxy !== false) {
      const proxyEnv = protocol.slice(0, -1) + "_proxy";
      const proxyUrl =
        process.env[proxyEnv] || process.env[proxyEnv.toUpperCase()];
      if (proxyUrl) {
        const parsedProxyUrl = url.parse(proxyUrl);
        const noProxyEnv = process.env.no_proxy || process.env.NO_PROXY;
        let shouldProxy = true;

        if (noProxyEnv) {
          const noProxy = noProxyEnv.split(",").map(function trim(s) {
            return s.trim();
          });

          shouldProxy = !noProxy.some(function proxyMatch(proxyElement) {
            if (!proxyElement) {
              return false;
            }
            if (proxyElement === "*") {
              return true;
            }
            if (
              proxyElement[0] === "." &&
              parsed.hostname.substr(
                parsed.hostname.length - proxyElement.length
              ) === proxyElement
            ) {
              return true;
            }

            return parsed.hostname === proxyElement;
          });
        }

        if (shouldProxy) {
          proxy = {
            host: parsedProxyUrl.hostname,
            port: parsedProxyUrl.port,
          };

          if (parsedProxyUrl.auth) {
            var proxyUrlAuth = parsedProxyUrl.auth.split(":");
            proxy.auth = {
              username: proxyUrlAuth[0],
              password: proxyUrlAuth[1],
            };
          }
        }
      }
    }

    if (proxy) {
      options.hostname = proxy.host;
      options.host = proxy.host;
      options.headers.host =
        parsed.hostname + (parsed.port ? ":" + parsed.port : "");
      options.port = proxy.port;
      options.path =
        protocol +
        "//" +
        parsed.hostname +
        (parsed.port ? ":" + parsed.port : "") +
        options.path;

      // 基本的代理授权
      if (proxy.auth) {
        const base64 = Buffer.from(
          proxy.auth.username + ":" + proxy.auth.password,
          "utf8"
        ).toString("base64");
        options.headers["Proxy-Authorization"] = "Basic " + base64;
      }
    }

    let transport;
    const isHttpsProxy =
      isHttpsRequest && (proxy ? isHttps.test(proxy.protocol) : true);
    if (config.transport) {
      transport = config.transport;
    } else if (config.maxRedirects === 0) {
      transport = isHttpsProxy ? https : http;
    } else {
      if (config.maxRedirects) {
        options.maxRedirects = config.maxRedirects;
      }
      transport = isHttpsProxy ? httpsFollow : httpFollow;
    }

    if (config.maxBodyLength > -1) {
      options.maxBodyLength = config.maxBodyLength;
    }

    // 创建请求
    const req = transport.request(options, function handleResponse(res) {
      if (req.aborted) return;

      // 如果需要，不压缩响应主体
      const stream = res;

      // 如果发生重定向，则返回最后一个请求
      const lastRequest = res.req || req;

      // 如果没有内容，则禁用HEAD请求或解压缩，则不应解压缩
      if (
        res.statusCode !== 204 &&
        lastRequest.method !== "HEAD" &&
        config.decompress !== false
      ) {
        switch (res.headers["content-encoding"]) {
          /*eslint default-case:0*/
          case "gzip":
          case "compress":
          case "deflate":
            // 将解压缩器添加到主体流处理管道中
            stream = stream.pipe(zlib.createUnzip());

            // 删除content-encoding，以免混淆下游操作
            delete res.headers["content-encoding"];
            break;
        }
      }

      const response = {
        status: res.statusCode,
        statusText: res.statusMessage,
        headers: res.headers,
        config: config,
        request: lastRequest,
      };

      if (config.responseType === "stream") {
        response.data = stream;
        settle(resolve, reject, response);
      } else {
        const responseBuffer = [];
        stream.on("data", function handleStreamData(chunk) {
          responseBuffer.push(chunk);

          // 确保内容长度不超过maxContentLength（如果指定）
          if (
            config.maxContentLength > -1 &&
            Buffer.concat(responseBuffer).length > config.maxContentLength
          ) {
            stream.destroy();
            reject(
              createError(
                "maxContentLength size of " +
                  config.maxContentLength +
                  " exceeded",
                config,
                null,
                lastRequest
              )
            );
          }
        });

        stream.on("error", function handleStreamError(err) {
          if (req.aborted) return;
          reject(enhanceError(err, config, null, lastRequest));
        });

        stream.on("end", function handleStreamEnd() {
          var responseData = Buffer.concat(responseBuffer);
          if (config.responseType !== "arraybuffer") {
            responseData = responseData.toString(config.responseEncoding);
          }

          response.data = responseData;
          settle(resolve, reject, response);
        });
      }
    });

    // 处理错误
    req.on("error", function handleRequestError(err) {
      if (req.aborted && err.code !== "ERR_FR_TOO_MANY_REDIRECTS") return;
      reject(enhanceError(err, config, null, req));
    });

    // 处理请求超时
    if (config.timeout) {
      //有时响应将非常慢，并且没有响应，connect事件将被事件循环系统阻止。
      //并且将触发计时器回调，并在连接之前调用abort（），然后获取“套接字挂起”和代码ECONNRESET。
      //此时，如果我们有大量请求，nodejs将在后台挂断一些套接字。而且这个数字会越来越大。
      //然后挂断的这些套接字将逐渐使CPU变差。
      //ClientRequest.setTimeout将在指定的毫秒内触发，并可以确保在连接后将触发abort（）。
      req.setTimeout(config.timeout, function handleRequestTimeout() {
        req.abort();
        reject(
          createError(
            "timeout of " + config.timeout + "ms exceeded",
            config,
            "ECONNABORTED",
            req
          )
        );
      });
    }

    if (config.cancelToken) {
      // 处理取消
      config.cancelToken.promise.then(function onCanceled(cancel) {
        if (req.aborted) return;

        req.abort();
        reject(cancel);
      });
    }

    // 发送请求
    if (utils.isStream(data)) {
      data
        .on("error", function handleStreamError(err) {
          reject(enhanceError(err, config, null, req));
        })
        .pipe(req);
    } else {
      req.end(data);
    }
  });
};
